Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow child one-to-one instances to be updated without providing PK #29

Closed
wants to merge 2 commits into from

Conversation

cjroth
Copy link

@cjroth cjroth commented Nov 16, 2017

This allows us to omit the pk when updating child one-to-one relations by inferring the pk from the parent when the child serializer is instantiated. Since a parent can only have one of a one-to-one child instance, it makes sense to assume which instance we mean to update.

Before:

        serializer = serializers.UserSerializer(
            instance=user,
            data={
                'pk': user_pk,
                'username': 'new',
                'profile': {
                    'pk': profile_pk,
                    'access_key': None,
                    # ...

After

        serializer = serializers.UserSerializer(
            instance=user,
            data={
                # omit pk
                'username': 'new',
                'profile': {
                    'pk': profile_pk,
                    'access_key': None,
                    # ...

@codecov-io
Copy link

codecov-io commented Nov 16, 2017

Codecov Report

Merging #29 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #29      +/-   ##
==========================================
+ Coverage   99.34%   99.35%   +0.01%     
==========================================
  Files           3        3              
  Lines         153      156       +3     
==========================================
+ Hits          152      155       +3     
  Misses          1        1
Impacted Files Coverage Δ
drf_writable_nested/mixins.py 99.3% <100%> (+0.01%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d3e199c...53adde6. Read the comment docs.

@ruscoder
Copy link
Member

Thanks for the contribution. I'll review this PR on weekends.

@ruscoder
Copy link
Member

@cjroth

It seems you are trying to use an instance pk instead of a relation pk.

What about cases, when we have User model and, for example, Profile model. Profile is one to one relation to User, but not mandatory.
So, we can have a user with pk=100 and a profile with pk=90.

@@ -115,6 +115,9 @@ def update_or_create_reverse_relations(self, instance, reverse_relations):
if related_data is None:
# Skip processing for empty data
continue
pk_name = field.Meta.model._meta.pk.attname
if pk_name not in related_data:
related_data[pk_name] = instance.pk
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the line related_data[pk_name] = instance.pk should be replaced with something like that:

related_instance = getattr(instance, field_source, None)
if related_instance:
    related_data[pk_name] = related_instance.pk

@@ -115,6 +115,9 @@ def update_or_create_reverse_relations(self, instance, reverse_relations):
if related_data is None:
# Skip processing for empty data
continue
pk_name = field.Meta.model._meta.pk.attname
if pk_name not in related_data:
Copy link
Member

@ruscoder ruscoder Nov 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The related_data can contain pk attribute instead of pk_name which usually equals to id

@ckcollab
Copy link

ckcollab commented Jan 25, 2018

@cjroth sorry to bother, but is still in a good state/up to date? I can give it a test if so! I'm getting some problems with the parent relationship not being created and believe this should fix it.

EDIT: Tested it against this and I got a "field is required" error creating the parent:

# models
class Competition(models.Model):
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="competitions")
    created_when = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=256)


class Page(models.Model):
    competition = models.ForeignKey(Competition, related_name='pages', on_delete=models.DO_NOTHING)
    title = models.TextField(max_length=255)
    content = models.TextField()
    index = models.PositiveIntegerField()


# Serializers
class PageSerializer(serializers.ModelSerializer):  # also tried WriteableNestedModelSerializer here
    # *NOTE* The competition property has to be replicated at the end of the file
    # after the CompetitionSerializer class is declared
    # competition = CompetitionSerializer(many=True)

    class Meta:
        model = Page
        fields = (
            'competition',
            'title',
            'content',
            'index',
        )


class CompetitionSerializer(WritableNestedModelSerializer):
    created_by = serializers.SerializerMethodField(read_only=True)
    pages = PageSerializer(many=True)

    class Meta:
        model = Competition
        fields = (
            'id',
            'title',
            'created_by',
            'pages',
        )

PageSerializer.competition = CompetitionSerializer(many=True)  # also tried with explicit source definition

Given:

{
    "title": "asdf",
    "pages": [{"title": "test", "content": "test","index":"1"}]
}

Result is:

{
    "pages": [
        {
            "competition": [
                "This field is required."
            ]
        }
    ]
}

I could be doing something else stupid and/or misunderstanding this, as well...

@cjroth
Copy link
Author

cjroth commented Jan 25, 2018

I haven't looked at it since this PR but we are using my branch with no issues at the moment. I will try to look at it when I get some time. Sorry to be MIA!

@ckcollab
Copy link

So to anyone else who runs into my problem, I think I just misunderstood how drf-writable-nested works. I needed to add the required=False kwargs and let the module do its job! Validation was stopping it, drf-writable-nested doesn't remove this validation for you.

class PageSerializer(WritableNestedModelSerializer):
    class Meta:
        model = Page
        fields = (
            'competition',
        )
        extra_kwargs = {
            # This was key!
            "competition": {"required": False}
        }

Thanks!

@thongly
Copy link

thongly commented Feb 7, 2018

I had a similar situation, where removing the field from class Meta made the difference.

The extra_kwargs didn't seem to matter. Rather, removing competition from the fields was the only way we could get it to work.

There's some perhaps inconsistent or unexpected behavior that we should write out in the docs. I'll try to do that shortly.

On another note, is #29 ready to be merged?

@ruscoder
Copy link
Member

ruscoder commented Feb 8, 2018

I think this MR contains something that can brake another part of logic. See my comments to this MR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants